/**
 * Processes a bundle using earth.nullschool.net/flat-1.0 conventions.
 */

import {clamp} from "../util/math.js";
import * as utc from "../util/utc.js";
import * as _ from "underscore";
import {regularGrid} from "../grid/regular.js";
import * as nearest from "../interpolate/nearest.js";
import * as bilinear from "../interpolate/bilinear.js";
import * as decoder from "../codec/decoder.js";
import {castBool} from "../util/values.js";

/*
Example:

{
  "Conventions": "earth.nullschool.net/flat-1.0",
  "title": "Probability of Visible Aurora",
  "institution": "SWPC / NCEP / NWS / NOAA",
  "source": "OVATION Aurora Short Term Forecast",
  "references": "http://www.swpc.noaa.gov/products/aurora-30-minute-forecast",
  "units": "%",

  "valid_range": [lo, hi],   // CONSIDER: formalize this
  "actual_range": [lo, hi],  // CONSIDER: formalize this
  "has_missing": false,      // CONSIDER: formalize this. true when data contains a missing/null value, false when it
                                          definitely has no missing values, and undefined when unknown

  "date": "2018-01-29T14:10Z",
  "init": "2018-01-29T13:40Z",
  "lon": {
    "start": -180,
    "delta": 0.3515625,
    "size": 1024
  },
  "lat": {
    "start": -89.82421875,
    "delta": 0.3515625,
    "size": 512
  },
  "encoding": {"type": "packed_delta_rle"},
  "data": [
    [[0,1024]]
  ]
}

*/

export function buildFlatProduct(bundle) {
    const {
        Conventions: conventions,
        // title,
        // institution,
        source,
        // references,
        // units,
        has_missing,
        date,
        // init,
        encoding,
    } = bundle;

    if (conventions !== "earth.nullschool.net/flat-1.0") {
        throw new Error(`Unsupported data conventions: ${_.escape(conventions)}`);
    }

    // Protect against malicious values.
    const lon = {start: +bundle.lon.start, delta: +bundle.lon.delta, size: clamp(+bundle.lon.size, 0, 8192)};
    const lat = {start: +bundle.lat.start, delta: +bundle.lat.delta, size: clamp(+bundle.lat.size, 0, 8192)};
    const rows = Array.isArray(bundle.data) ? bundle.data : [];

    let decodeRow = row => row;
    if (encoding) {
        switch (encoding.type) {
            case "packed_delta_rle":{
                const {scale_factor = 1, add_offset = 0} = encoding;
                decodeRow = row => decoder.decodePackedDeltaRle(row, +scale_factor, +add_offset, lon.size);}
                break;
            default:
                throw new Error(`Unsupported encoding: ${_.escape(encoding.type)}`);
        }
    }

    const data = new Float32Array(lon.size * lat.size);
    let i = 0;
    rows.filter(Array.isArray).forEach(row => {
        const decoded = decodeRow(row);
        data.set(decoded, i);
        i += decoded.length;
    });
    data.containsNaN = castBool(has_missing);

    const grid = regularGrid(lon, lat);
    const field = {
        valueAt: i => data[i],
        scalarize: x => x,
        isDefined: i => !isNaN(data[i]),
        nearest: nearest.scalar(grid, data),
        bilinear: bilinear.scalar(grid, data),
    };

    return {
        source: _.escape(source),
        validTime: () => utc.parts(date),
        grid: () => grid,
        field: () => field,
        valueInRange(t) { return this.scale.valueInRange(t); },
    };
}
